package ws.performancetest.api;

import java.awt.Desktop;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.hyperic.sigar.SigarException;
import ws.performancetest.api.annotation.Ignore;
import ws.performancetest.api.annotation.PerformanceTest;
import ws.performancetest.api.exception.DatabaseConnectionRequiredException;
import ws.performancetest.api.exception.InvalidTestConfiguration;
import ws.performancetest.api.exception.TestSuiteSetupException;
import ws.utils.jdbc.DBConnection;
import ws.utils.io.FileUtil;
import ws.utils.jdbc.DBManager;
import ws.utils.thread.ThreadManager;

/**
 *
 * @author Wilson Santos, 36228 <wilson.santos@ua.pt>
 */
public class AbstractTestSuite implements TestSuite {

  protected static final boolean DEBUG = true;
  // <editor-fold defaultstate="collapsed" desc="Object Properties">
  protected int testId;
  protected ThreadManager threadManager;
  /** Database connection to save the results. */
  protected Connection con;
  /** Connection url that might be used on some JDBC tests that extend this abstract suite. */
  protected String dburl;
  /** System information of this machine. */
  protected DBSystemInfo sysInfo;
  /** This thread runs the tests. */
  protected Thread testRunnerThread;
  /** Database to be used on the tests. */
  protected String database;
  /** Access time log. This list has strings with SQL code to insert the test results on the database. */
  protected final Collection<String> logList = new LinkedBlockingQueue<String>();
  /** Name of the test suite. */
  protected String name = null;
  /** Name of the table that holds the system information. */
  protected String systemInfoTable = "SystemInfo";
  /** Name of the table that holds the test results. */
  protected String resultsTable = "";
  /** Name of the table/view that holds the test results summary. */
  protected String summaryTable = "";
  /** Name of the table that holds the tests configurations. */
  protected String testConfigurationTable = "TestConfiguration";
  /** Name of the user-defined function that finds test configuration. */
  protected String udfTestConfigurationFind = "udf_TestConfiguration_find";
  /** Name of the user-defined function that finds system information. */
  protected String udfTestSystemInfoFind = "udf_SystemInfo_find";
  /** Name of the stored procedure that saves the test configuration. */
  protected String spSaveConfig = "sp_TestConfiguration_save";
  /** Name of the SQL script that will be used for inserting the results in the database. */
  protected String scriptname = "";
  /** Name of the current test being executed. */
  protected String currentTest = "";
  /** Thread cycle count. */
  private Integer[] cycleAmountList;
  /** Number of test samples. Each test on the suite will produce a number of results equal to this value. */
  protected int samplesCount = 1;
  /** Number of threads running the test. */
  protected int numberOfThreads = 1;
  /** Number of cycle for the current test. */
  protected int cycleCount = 1;
  /** Time to let the CPU sleep. */
  private final long timeout = 70;
  /** List with thread amounts. The tests on the suite run for each amount. */
  protected Integer[] threadAmountList;
  protected String sql_createSystemInfoTable = "CREATE TABLE [dbo].[SystemInfo](\n"
          + "[ID] [int] IDENTITY(1,1) NOT NULL PRIMARY KEY,\n"
          + "	[Name] [nvarchar](50) NOT NULL,\n"
          + "	[CPU] [nvarchar](100) NOT NULL,\n"
          + "	[RAM] [varchar](10) NOT NULL,\n"
          + "	[OperatingSystem] [varchar](40) NOT NULL,\n"
          + ")";
  /** SQL code to create the results table. Each test suite defines its results table. */
  protected String sql_createResultsTable = "CREATE TABLE ? (\n"
          + "TestId int NOT NULL,"
          + "Type nchar(150) NOT NULL,"
          + "NumberOfThreads int NOT NULL,"
          + "Cycle int NOT NULL,"
          + "ExecTime bigint NOT NULL,"
          + "SetupTime bigint,"
          + "SysInfo int NOT NULL REFERENCES SystemInfo(ID),"
          + "SampleId int IDENTITY"
          + ")";
  /** SQL code to create the table/view that show the test results. */
  protected String sql_createSummary = "CREATE VIEW ?summary \n"
          + "AS "
          + "SELECT  TestId, Type, NumberOfThreads, Cycle, ExecT, SetupT, ExecT+SetupT AS Total, SysInfo FROM "
          + "( "
          + "SELECT  TestId, Type, NumberOfThreads, Cycle, AVG(ExecTime) AS ExecT, AVG(SetupTime) AS SetupT, SysInfo "
          + "FROM ?result T1 "
          + " WHERE SampleId NOT IN "
          + "( "
          + "SELECT TOP 10 SampleID "
          + "	FROM ?result  "
          + "	WHERE TestId=T1.TestId AND Type=T1.Type AND SysInfo=T1.SysInfo AND NumberOfThreads=T1.NumberOfThreads AND Cycle=T1.Cycle  "
          + "	ORDER BY ExecTime ASC "
          + "UNION "
          + "SELECT TOP 10 SampleId  "
          + "	FROM ?result  "
          + "	WHERE TestId=T1.TestId AND Type=T1.Type AND SysInfo=T1.SysInfo AND NumberOfThreads=T1.NumberOfThreads AND Cycle=T1.Cycle  "
          + "	ORDER BY ExecTime DESC "
          + ")  "
          + "GROUP BY SysInfo, TestId, Type, NumberOfThreads, Cycle "
          + ") T";
  protected String sql_createTestConfigurationTable = "CREATE TABLE [dbo].[TestConfiguration](\n"
          + "	[Name] [nchar](30) NOT NULL,\n"
          + "	[ThreadAmountList] [nchar](150) NOT NULL,\n"
          + "	[CycleAmountList] [nchar](150) NOT NULL,\n"
          + "	[Samples] [int] NOT NULL\n"
          + ")";
  protected String sql_createTestConfigurationFindUDF = "CREATE FUNCTION [dbo].[udf_TestConfiguration_find] (@name nvarchar(30))\n"
          + "RETURNS nvarchar(30)\n"
          + "AS\n"
          + "BEGIN\n"
          + "  DECLARE @ret nvarchar(30) = NULL\n"
          + "  SELECT @ret=Name FROM TestConfiguration WHERE Name=@name\n"
          + "  RETURN @ret\n"
          + "END";
  protected String sql_createSystemInfoFindUDF = "CREATE FUNCTION [dbo].[udf_SystemInfo_find] (@name varchar(30))\n"
          + "RETURNS int\n"
          + "AS\n"
          + "BEGIN\n"
          + "  DECLARE @ID int = NULL\n"
          + "  SELECT @ID=ID FROM SystemInfo WHERE Name=@name\n"
          + "  RETURN @ID\n"
          + "END";
  protected String sql_createSaveConfigSP = "CREATE PROCEDURE [dbo].[sp_TestConfiguration_save]\n"
          + "@name nvarchar(30),\n"
          + "@threads nvarchar(150),\n"
          + "@cycles nvarchar(150),\n"
          + "@samples int\n"
          + "AS\n"
          + " -- if exists update values.\n"
          + " IF (SELECT dbo.udf_TestConfiguration_find(@name)) IS NOT NULL\n"
          + "  BEGIN\n"
          + "    UPDATE TestConfiguration SET\n"
          + "      ThreadAmountList=@threads, CycleAmountList=@cycles, Samples=@samples\n"
          + "      WHERE Name=@name\n"
          + "  END\n"
          + "  -- else, insert new test suite configuration.\n"
          + "  ELSE\n"
          + "  BEGIN\n"
          + "    INSERT INTO TestConfiguration\n"
          + "	  (Name, ThreadAmountList, CycleAmountList, Samples)\n"
          + "	  VALUES (@name, @threads, @cycles, @samples)\n"
          + "  END\n";
  // </editor-fold>

  // <editor-fold defaultstate="collapsed" desc="Constructors">
  /**
   * Create new instance.
   * On object creation the system information is gathered and is there is a
   * database connection this information will be inserted.
   *
   * @throws TestSuiteSetupException cannot gather system information.
   */
  protected AbstractTestSuite() throws TestSuiteSetupException {
    try {
      /* Gather system information. */
      sysInfo = new DBSystemInfo(true);
    } catch (SigarException ex) {
      throw new TestSuiteSetupException(ex);
    }
  }

  /**
   * Create new instance.
   * This constructor does the same has the no-arguments constructor, plus sets
   * the {@link #resultsTable} and if there is a database connection creates it.
   *
   * @param resultsTable Name of the table that holds the test results.
   * @param summaryTable Name of the table/view that holds the test results summary.
   *
   * @throws TestSuiteSetupException if database occurs; or cannot gather system information.
   */
  protected AbstractTestSuite(String resultsTable, String summaryTable) throws TestSuiteSetupException {
    this();
    try {
      this.resultsTable = resultsTable;
      this.summaryTable = summaryTable;
      this.threadAmountList = new Integer[]{numberOfThreads};
      if (DBConnection.isOnline()) {
        con = DBConnection.getDBCon();
        DBManager dbmanager = new DBManager();
        Statement stmt = con.createStatement();

        /* Create system info table. */
        if (!dbmanager.existsTable(systemInfoTable)) {
          stmt.executeUpdate(sql_createSystemInfoTable);
        }
        /* Create results table. */
        if (!dbmanager.existsTable(resultsTable)) {
          sql_createResultsTable = sql_createResultsTable.replace("?", resultsTable);
          stmt.executeUpdate(sql_createResultsTable);
        }
        /* Create summary table. */
        if (!dbmanager.existsTable(summaryTable)) {
          sql_createSummary = sql_createSummary.replace("?summary", summaryTable);
          sql_createSummary = sql_createSummary.replace("?result", resultsTable);
          stmt.executeUpdate(sql_createSummary);
        }
        /* Create test configuration table. */
        if (!dbmanager.existsTable(testConfigurationTable)) {
          stmt.executeUpdate(sql_createTestConfigurationTable);
        }
        /* Create find test configuration udf. */
        if (!dbmanager.existsUDF(udfTestConfigurationFind)) {
          stmt.executeUpdate(sql_createTestConfigurationFindUDF);
        }
        /* Create find system configuration udf. */
        if (!dbmanager.existsUDF(udfTestSystemInfoFind)) {
          stmt.executeUpdate(sql_createSystemInfoFindUDF);
        }
        /* Create save configuration sp. */
        if (!dbmanager.existsSP(spSaveConfig)) {
          stmt.executeUpdate(sql_createSaveConfigSP);
        }

        stmt.close();

        /* Insert test platform into database. */
        sysInfo.insert();
      }
    } catch (SQLException ex) {
      throw new TestSuiteSetupException(ex);
    }
  }
  // </editor-fold>

  /**
   * {@inheritDoc}
   *
   * <p>All methods marked with {@link PerformanceTest} are invoked.</p>
   */
  @Override
  public void runAllTests() throws Exception {
    List<Test> performanceTestList = getTestList();
    runSelectedTests(performanceTestList);
  }

  /**
   * {@inheritDoc}
   *
   * <p>Only the performance tests on the supplied list are invoked.</p>
   * <p>
   * After each thread amount, the current results will be dumped to the selected
   * permanent storage medium (database or file), to avoid having to much logging
   * held in memory. This selection can be done using {@link #toDB} flag.
   * </p>
   *
   * @param performanceTestList list of performance tests to be executed.
   *
   * @throws Exception
   */
  @Override
  public void runSelectedTests(final List<Test> performanceTestList) throws Exception {
    long t_start, t_finish;
    if (DEBUG) {
      System.out.print("Running test suite: '" + this.toString() + "'");
      System.out.println(" {threads=" + Arrays.asList(threadAmountList) + ", samples=" + samplesCount + ", cycles=" + Arrays.asList(cycleAmountList) + "}");
      t_start = System.currentTimeMillis();
    }

    /* Some test suites may require a existing database connection. */
    if (requiresDBConnection() && !DBConnection.isOnline()) {
      throw new DatabaseConnectionRequiredException(this);
    }

    /* If the sysInfo.ID is not set, we are working offline so we must
     * insert in the script file the code to get the ID later, when the script is executed.
     */
    if (sysInfo.getID() == null) {
      logList.add("DECLARE @sysInfoId int\n");
      logList.add("EXEC @sysInfoId = sp_SystemInfo_insert @name='" + sysInfo.getName() + "', @cpu='" + sysInfo.getCpu() + "', @ram='" + sysInfo.getRam() + "', @os='" + sysInfo.getOperatingSystem() + "'\n");
    }

    ResultSet trs = new DBManager().getResultSet("SELECT TOP 1 TestId FROM " + resultsTable + " ORDER BY TestId DESC");
    if (trs.next()) {
      testId = trs.getInt(1) + 1;
    } else {
      testId = 1;
    }

    /* Run the tests. */
    final AbstractTestSuite instance = this;
    testRunnerThread = new Thread(new Runnable() {

      @Override
      public void run() {
        for (Test method : performanceTestList) {
          for (int threadCount : threadAmountList) {
            numberOfThreads = threadCount;
            for (int cycle : cycleAmountList) {
              cycleCount = cycle;
              for (int i = 1; i <= samplesCount; ++i) {
                try {
                  currentTest = method.toString();
                  if (DEBUG) {
                    System.out.println("test=" + currentTest + " {threads=" + numberOfThreads + ", cycle=" + cycleCount + ", sample=" + i + "}");
                  }
                  method.unwrap().invoke(instance, new Object[0]);
                  System.gc();
                  /* Let CPU rest. */
                  Thread.sleep(timeout);
                  /* Notify when test finished. */
                  synchronized (instance) {
                    instance.notifyAll();
                  }
                } catch (IllegalAccessException ex) {
                  Logger.getLogger(AbstractTestSuite.class.getName()).log(Level.SEVERE, null, ex);
                } catch (IllegalArgumentException ex) {
                  Logger.getLogger(AbstractTestSuite.class.getName()).log(Level.SEVERE, null, ex);
                } catch (InvocationTargetException ex) {
                  Logger.getLogger(AbstractTestSuite.class.getName()).log(Level.SEVERE, null, ex);
                } catch (InterruptedException ex) {
                  Logger.getLogger(AbstractTestSuite.class.getName()).log(Level.SEVERE, null, ex);
                }
              }
              try {
                if (DBConnection.isOnline()) {
                  writeLogToDatabase();
                } else {
                  writeLogToFile();
                }
              } catch (final SQLException ex) {
                Logger.getLogger(AbstractTestSuite.class.getName()).log(Level.SEVERE, null, ex);
              } catch (final IOException ex) {
                Logger.getLogger(AbstractTestSuite.class.getName()).log(Level.SEVERE, null, ex);
              }
            }
          }
        }
      }
    });
    testRunnerThread.setPriority(Thread.MAX_PRIORITY);
    testRunnerThread.start();
    try {
      testRunnerThread.join();
    } catch (InterruptedException ex) {
      System.out.println("Interrupted while executing: " + "test=" + currentTest + " {threads=" + numberOfThreads + ", cycle=" + cycleCount + "}");
    }

    if (DEBUG) {
      t_finish = System.currentTimeMillis();
      System.out.println("Finished! Time elapsed: " + (t_finish - t_start) / 6e4 + " minutes.");
    }


    if (!DBConnection.isOnline()) {
      Desktop desktop = Desktop.getDesktop();
      desktop.open(new File(scriptname));
    }
  }

  private void memstat(String at) {
    Runtime r = Runtime.getRuntime();
    // amount of unallocated memory
    long free = r.freeMemory();
    // total amount of memory available allocated and unallocated.
    long total = r.totalMemory();
    // total amount of allocated memory
    long inuse = r.totalMemory() - r.freeMemory();
    // max memory JVM will attempt to use
    long max = r.maxMemory();

    System.out.printf("%s %d/%d\n", at, free, total);
  }

  /**
   * Add the current test results to the list of results.
   *
   * This one is protected because is for internal purposes only.
   *
   * @param time average access time.
   */
  protected synchronized void addLog(long time) {
    String sysInfoId = sysInfo.getID() != null ? sysInfo.getID().toString() : "@sysInfoId";

    logList.add("INSERT INTO " + resultsTable + " (TestId, NumberOfThreads, Cycle, Type, ExecTime, SysInfo) "
            + "VALUES (" + testId + ", " + numberOfThreads + ", " + cycleCount + ", '" + currentTest + "', " + time + ", " + sysInfoId + ")\n");
  }

  /**
   * Add the current test results to the list of results.
   *
   * This one is protected because is for internal purposes only.
   *
   * @param execTime average access time.
   */
  protected synchronized void addLog(long execTime, long setupTime) {
    String sysInfoId = sysInfo.getID() != null ? sysInfo.getID().toString() : "@sysInfoId";

    logList.add("INSERT INTO " + resultsTable + " (TestId, NumberOfThreads, Cycle, Type, ExecTime, SetupTime, SysInfo) "
            + "VALUES (" + testId + ", " + numberOfThreads + ", " + cycleCount + ", '" + currentTest + "', " + execTime + ", " + setupTime + ", " + sysInfoId + ")\n");
  }

  /**
   * Add the current test results to the list of results.
   *
   * @param name test name.
   * @param time average access time.
   */
  @Override
  public synchronized void addLog(String name, long time) {
    String sysInfoId = sysInfo.getID() != null ? sysInfo.getID().toString() : "@sysInfoId";

    logList.add("INSERT INTO " + resultsTable + " (TestId, NumberOfThreads, Cycle, Type, AccessTime, SysInfo) "
            + "VALUES (" + testId + ", " + numberOfThreads + ", " + cycleCount + ", '" + name + "', " + time + ", " + sysInfoId + ")\n");
  }

  /**
   * {@inheritDoc}.
   *
   * Insert the results held in memory into the database. After successfully
   * inserted they will be removed from memory.
   * 
   * @throws SQLException
   */
  @Override
  public synchronized void writeLogToDatabase() throws SQLException {
    con = DBConnection.getDBCon();
    Statement stmt = con.createStatement();
    try {
      con.setAutoCommit(false);
      for (String sql : logList) {
        stmt.addBatch(sql);
      }
      stmt.executeBatch();
      logList.clear();
      con.commit();
    } catch (SQLException ex) {
      con.rollback();
      throw ex;
    } finally {
      con.setAutoCommit(true);
    }
  }

  /**
   * {@inheritDoc}.
   *
   * Append the current results to a SQL script.
   * The log will be cleared.
   * If the {@link #scriptname} is not properly setup this method does nothing.
   *
   * @throws IOException
   */
  @Override
  public void writeLogToFile() throws IOException {
    if (DEBUG) {
      System.out.println("writeLogToFile(): " + scriptname);
    }
    if (scriptname.isEmpty()) {
      return;
    }
    for (String s : logList) {
      FileUtil.appendTextToFile(s, scriptname);
    }
    logList.clear();
  }

  @Override
  public boolean hasPreviousResults(List<Test> testList) throws SQLException {
    boolean ret = false;
    if (DBConnection.isOnline() && !testList.isEmpty()) {
      con = DBConnection.getDBCon();
      Statement stmt = con.createStatement();
      for (Test test : testList) {
        String sql = "SELECT * FROM " + resultsTable + " WHERE Type='" + test.toString() + "' AND SysInfo=" + sysInfo.getID();
        if (stmt.executeQuery(sql).next()) {
          ret = true;
          break;
        }
      }
      stmt.close();
    } else {
      ret = new File(scriptname).exists();
    }
    return ret;
  }

  @Override
  public void removePreviousResults(List<Test> testList) throws SQLException {
    if (testList.isEmpty()) {
      return;
    }

    if (DBConnection.isOnline()) {
      con = DBConnection.getDBCon();
      con.setAutoCommit(false);
      try {
        Statement stmt = con.createStatement();
        for (Test test : testList) {
          String sql = "DELETE FROM " + resultsTable + " WHERE Type='" + test.toString() + "' AND SysInfo=" + sysInfo.getID();
          stmt.addBatch(sql);
        }
        stmt.executeBatch();
        stmt.close();
        con.commit();
      } catch (SQLException ex) {
        con.rollback();
        throw ex;
      } finally {
        con.setAutoCommit(true);
      }
    } else {
      new File(scriptname).delete();
    }
  }

  @Override
  public void saveConfiguration() throws SQLException, InvalidTestConfiguration {
    // <editor-fold defaultstate="collapsed" desc="Pre-Conditions">
    if (threadAmountList.length < 1) {
      throw new InvalidTestConfiguration("Thread amount list is empty.");
    }
    if (cycleAmountList.length < 1) {
      throw new InvalidTestConfiguration("Cycle amount list is empty.");
    }
    if (samplesCount < 1) {
      throw new InvalidTestConfiguration("'Samples' value should be equal or greater than 1.");
    }
    // </editor-fold>
    if (DBConnection.isOnline()) {
      String sql = "{ call sp_TestConfiguration_save(?, ?, ?, ?) }";
      CallableStatement stmt = DBConnection.getDBCon().prepareCall(sql);

      stmt.setString(1, this.toString());
      String threadList = threadAmountList[0].toString();
      for (int i = 1; i < threadAmountList.length; ++i) {
        threadList += "," + threadAmountList[i].toString();
      }
      stmt.setString(2, threadList);
      String cycleList = cycleAmountList[0].toString();
      for (int i = 1; i < cycleAmountList.length; ++i) {
        cycleList += "," + cycleAmountList[i].toString();
      }
      stmt.setString(3, cycleList);
      stmt.setInt(4, samplesCount);

      stmt.execute();
    }
  }

  @Override
  public boolean loadConfiguration() throws SQLException {
    if (DBConnection.isOnline()) {
      String sql = "SELECT ThreadAmountList, CycleAmountList, Samples FROM TestConfiguration "
              + "WHERE Name='" + this.toString() + "'";
      Statement stmt = DBConnection.getDBCon().createStatement();
      ResultSet rs = stmt.executeQuery(sql);

      if (rs.next()) {
        threadAmountList = parseStringList(rs.getString(1));
        cycleAmountList = parseStringList(rs.getString(2));
        samplesCount = rs.getInt(3);
        return true;
      } else {
        return false;
      }
    }
    return false;
  }

  /**
   * Parse comma-separated integer values into array.
   *
   * @param str string with comma-separated values.
   * @return
   */
  private Integer[] parseStringList(String str) {
    String[] strList = str.split(",");
    Integer[] ret = new Integer[strList.length];
    for (int i = 0; i < ret.length; ++i) {
      ret[i] = Integer.parseInt(strList[i].trim());
    }
    return ret;
  }

  @Override
  public void interrupt() {
    if (testRunnerThread != null && !testRunnerThread.isInterrupted()) {
      if (threadManager != null) {
        threadManager.interrupt();
      }
      testRunnerThread.interrupt();
    }
  }

  /**
   * Check if the test suite requires a database connection to be performed.
   * 
   * @return {@code true} if it requires, otherwise {@code false}.
   */
  protected boolean requiresDBConnection() {
    return false;
  }

  /**
   * Get the name of this test suite.
   *
   * @return Name of this test suite.
   */
  @Override
  public String toString() {
    if (name == null) {
      name = this.getClass().getSimpleName();
    }
    return name;
  }

  // <editor-fold defaultstate="collapsed" desc="Getters and Setters">
  @Override
  public void setCycleAmountList(Integer[] newList) {
    this.cycleAmountList = newList;
  }

  @Override
  public int getSamplesCount() {
    return samplesCount;
  }

  @Override
  public void setSamplesCount(int count) {
    samplesCount = count;
  }

  @Override
  public int getTestCount() {
    return this.getClass().getDeclaredMethods().length * samplesCount;
  }

  @Override
  public Integer[] getThreadAmountList() {
    return threadAmountList;
  }

  @Override
  public void setThreadAmountList(Integer[] newList) {
    this.threadAmountList = newList;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public List<Test> getTestList() {
    Method[] methodList = this.getClass().getDeclaredMethods();
    List<Test> performanceTestList = new LinkedList<Test>();

    /* find the performance test methods. */
    for (Method method : methodList) {
      if (method.isAnnotationPresent(PerformanceTest.class) && !method.isAnnotationPresent(Ignore.class)) {
        performanceTestList.add(new Test(method));
      }
    }
    return performanceTestList;
  }

  @Override
  public Integer[] getCycleAmountList() {
    return cycleAmountList;
  }

  @Override
  public int getCycleCount() {
    return cycleAmountList.length;
  }

  @Override
  public int getThreadCount() {
    return threadAmountList.length;
  }

  @Override
  public void setDBUrl(String url) {
    dburl = url;
  }
  // </editor-fold> 
}
